#pragma once

#include <unordered_map>

#include "object/objectguid.h"

namespace battle::object
{
    struct WorldLocation;
    class Object;
    class Corpse;
    class Creature;
    class DynamicObject;
    class GameObject;
    class Pet;
    class Player;
    class Camera;
    class Unit;
    class WorldObject;
}

class Map;
namespace game::script
{

    struct AreaTriggerEntry;
    struct SpellEntry;
    class Aura;
    class CreatureAI;
    class GameObjectAI;
    class InstanceData;
    class Item;
    class Quest;
    class SpellCastTargets;

    enum DBScriptType
    {
        DBS_INTERNAL = -1,
        DBS_ON_QUEST_START = 0,
        DBS_ON_QUEST_END = 1,
        DBS_ON_GOSSIP = 2,
        DBS_ON_CREATURE_MOVEMENT = 3,
        DBS_ON_CREATURE_DEATH = 4,
        DBS_ON_SPELL = 5,
        DBS_ON_GO_USE = 6,
        DBS_ON_GOT_USE = 7,
        DBS_ON_EVENT = 8,
        DBS_ON_CREATURE_SPELL = 9,
        DBS_END = 10,
    };
#define DBS_START DBS_ON_QUEST_START

    enum ScriptedObjectType
    {
        SCRIPTED_UNIT = 0,    //CreatureScript
        SCRIPTED_GAMEOBJECT = 1,    //GameObjectScript
        SCRIPTED_ITEM = 2,    //ItemScript
        SCRIPTED_AREATRIGGER = 3,    //AreaTriggerScript
        SCRIPTED_SPELL = 4,    //SpellScript
        SCRIPTED_AURASPELL = 5,    //AuraScript
        SCRIPTED_MAPEVENT = 6,    //MapEventScript
        SCRIPTED_MAP = 7,    //ZoneScript
        SCRIPTED_BATTLEGROUND = 8,    //BattleGroundScript
        SCRIPTED_PVP_ZONE = 9,    //OutdoorPvPScript
        SCRIPTED_INSTANCE = 10,   //InstanceScript
        SCRIPTED_CONDITION = 11,   //ConditionScript
        SCRIPTED_ACHIEVEMENT = 12,   //AchievementScript
        SCRIPTED_MAX_TYPE
    };

    enum ScriptImplementation
    {
        SCRIPT_FROM_DATABASE = 0,
        SCRIPT_FROM_CORE = 1,
        SCRIPT_FROM_ELUNA = 2,
    };

    enum DBScriptCommand                                        // resSource, resTarget are the resulting Source/ Target after buddy search is done
    {
        SCRIPT_COMMAND_TALK = 0,            // resSource = WorldObject, resTarget = Unit/none
        // dataint = text entry from db_script_string -table. dataint2-4 optional for random selected texts.
        SCRIPT_COMMAND_EMOTE = 1,            // resSource = Unit, resTarget = Unit/none
        // datalong1 = emote_id, dataint1-4 optional for random selected emotes
        SCRIPT_COMMAND_FIELD_SET = 2,            // source = any, datalong = field_id, datalong2 = value
        SCRIPT_COMMAND_MOVE_TO = 3,            // resSource = Creature, datalong2 = travel_speed*100, x/y/z
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL: teleport unit to position
        SCRIPT_COMMAND_FLAG_SET = 4,            // source = any, datalong = field_id, datalong2 = bitmask
        SCRIPT_COMMAND_FLAG_REMOVE = 5,            // source = any, datalong = field_id, datalong2 = bitmask
        SCRIPT_COMMAND_TELEPORT_TO = 6,            // source or target with Player, datalong2 = map_id, x/y/z
        SCRIPT_COMMAND_QUEST_EXPLORED = 7,            // one from source or target must be Player, another GO/Creature, datalong=quest_id, datalong2=distance or 0
        SCRIPT_COMMAND_KILL_CREDIT = 8,            // source or target with Player, datalong = creature entry (or 0 for target-entry), datalong2 = bool (0=personal credit, 1=group credit)
        SCRIPT_COMMAND_RESPAWN_GO = 9,            // source = any, datalong=db_guid, datalong2=despawn_delay
        SCRIPT_COMMAND_TEMP_SUMMON_CREATURE = 10,           // source = any, datalong=creature entry, datalong2=despawn_delay
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL = summon active
        // dataint = (bool) setRun; 0 = off (default), 1 = on
        SCRIPT_COMMAND_OPEN_DOOR = 11,           // datalong=db_guid (or not provided), datalong2=reset_delay
        SCRIPT_COMMAND_CLOSE_DOOR = 12,           // datalong=db_guid (or not provided), datalong2=reset_delay
        SCRIPT_COMMAND_ACTIVATE_OBJECT = 13,           // source = unit, target=GO
        SCRIPT_COMMAND_REMOVE_AURA = 14,           // resSource = Unit, datalong = spell_id
        SCRIPT_COMMAND_CAST_SPELL = 15,           // resSource = Unit, cast spell at resTarget = Unit
        // datalong=spellid
        // dataint1-4 optional for random selected spell
        // data_flags &  SCRIPT_FLAG_COMMAND_ADDITIONAL = cast triggered
        SCRIPT_COMMAND_PLAY_SOUND = 16,           // resSource = WorldObject, target=any/player, datalong (sound_id), datalong2 (bitmask: 0/1=target-player, 0/2=with distance dependent, 0/4=map wide, 0/8=zone wide; so 1|2 = 3 is target with distance dependent)
        SCRIPT_COMMAND_CREATE_ITEM = 17,           // source or target must be player, datalong = item entry, datalong2 = amount
        SCRIPT_COMMAND_DESPAWN_SELF = 18,           // resSource = Creature, datalong = despawn delay
        SCRIPT_COMMAND_PLAY_MOVIE = 19,           // target can only be a player, datalog = movie id
        SCRIPT_COMMAND_MOVEMENT = 20,           // resSource = Creature. datalong = MovementType (0:idle, 1:random or 2:waypoint), datalong2 = wander-distance
        // data_flags &  SCRIPT_FLAG_COMMAND_ADDITIONAL = Random-movement around current position
        SCRIPT_COMMAND_SET_ACTIVEOBJECT = 21,           // resSource = Creature
        // datalong=bool 0=off, 1=on
        SCRIPT_COMMAND_SET_FACTION = 22,           // resSource = Creature
        // datalong=factionId, datalong2=faction_flags
        SCRIPT_COMMAND_MORPH_TO_ENTRY_OR_MODEL = 23,           // resSource = Creature, datalong=creature entry/modelid
        // data_flags &  SCRIPT_FLAG_COMMAND_ADDITIONAL = use datalong value as modelid explicit
        SCRIPT_COMMAND_MOUNT_TO_ENTRY_OR_MODEL = 24,           // resSource = Creature, datalong=creature entry/modelid
        // data_flags &  SCRIPT_FLAG_COMMAND_ADDITIONAL = use datalong value as modelid explicit
        SCRIPT_COMMAND_SET_RUN = 25,           // resSource = Creature
        // datalong= bool 0=off, 1=on
        SCRIPT_COMMAND_ATTACK_START = 26,           // resSource = Creature, resTarget = Unit
        SCRIPT_COMMAND_GO_LOCK_STATE = 27,           // resSource = GameObject
        // datalong= 1=lock, 2=unlock, 4=set not-interactable, 8=set interactable
        SCRIPT_COMMAND_STAND_STATE = 28,           // resSource = Creature
        // datalong = stand state (enum UnitStandStateType)
        SCRIPT_COMMAND_MODIFY_NPC_FLAGS = 29,           // resSource = Creature
        // datalong=NPCFlags
        // datalong2:0x00=toggle, 0x01=add, 0x02=remove
        SCRIPT_COMMAND_SEND_TAXI_PATH = 30,           // datalong = taxi path id (source or target must be player)
        SCRIPT_COMMAND_TERMINATE_SCRIPT = 31,           // datalong = search for npc entry if provided
        // datalong2= search distance
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL: terminate steps of this script if npc found
        //                                        ELSE: terminate steps of this script if npc not found
        // dataint=diff to change a waittime of current Waypoint Movement
        SCRIPT_COMMAND_PAUSE_WAYPOINTS = 32,           // resSource = Creature
        // datalong = 0: unpause waypoint 1: pause waypoint
        SCRIPT_COMMAND_JOIN_LFG = 33,           // datalong = zoneId;
        SCRIPT_COMMAND_TERMINATE_COND = 34,           // datalong = condition_id, datalong2 = if != 0 then quest_id of quest that will be failed for player's group if the script is terminated
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL terminate when condition is false ELSE terminate when condition is true
        SCRIPT_COMMAND_SEND_AI_EVENT_AROUND = 35,           // resSource = Creature, resTarget = Unit
        // datalong = AIEventType
        // datalong2 = radius
        SCRIPT_COMMAND_TURN_TO = 36,           // resSource = Unit, resTarget = Unit/none
        SCRIPT_COMMAND_MOVE_DYNAMIC = 37,           // resSource = Creature, resTarget Worldobject.
        // datalong = 0: Move resSource towards resTarget
        // datalong != 0: Move resSource to a random point between datalong2..datalong around resTarget.
        // orientation != 0: Obtain a random point around resTarget in direction of orientation
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL Obtain a random point around resTarget in direction of resTarget->GetOrientation + orientation
        // for resTarget == resSource and orientation == 0 this will mean resSource moving forward
        SCRIPT_COMMAND_SEND_MAIL = 38,           // resSource WorldObject, can be NULL, resTarget Player
        // datalong: Send mailTemplateId from resSource (if provided) to player resTarget
        // datalong2: AlternativeSenderEntry. Use as sender-Entry
        // dataint1: Delay (>= 0) in Seconds
        SCRIPT_COMMAND_CHANGE_ENTRY = 39,           // resSource = Creature, datalong=creature entry
        // dataint1 = entry
        SCRIPT_COMMAND_DESPAWN_GO = 40,           // resTarget = GameObject
        SCRIPT_COMMAND_RESPAWN = 41,           // resSource = Creature. Requires SCRIPT_FLAG_BUDDY_IS_DESPAWNED to find dead or despawned targets
        SCRIPT_COMMAND_SET_EQUIPMENT_SLOTS = 42,           // resSource = Creature, datalong = reset default 0(false) | 1(true)
        // dataint = main hand slot, dataint2 = offhand slot, dataint3 = ranged slot
        SCRIPT_COMMAND_RESET_GO = 43,           // resTarget = GameObject
        SCRIPT_COMMAND_UPDATE_TEMPLATE = 44,           // resSource = Creature
        // datalong = new Creature entry
        // datalong2 = Alliance(0) Horde(1), other values throw error
        SCRIPT_COMMAND_XP_USER = 53,           // source or target with Player, datalong = bool (0=off, 1=on)
        SCRIPT_COMMAND_SET_FLY = 59,           // resSource = Creature
        // datalong = bool 0=off, 1=on
        // data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL set/unset byte flag UNIT_BYTE1_FLAG_FLY_ANIM
        // dataint1: Delay (>= 0) in Seconds
    };

#define MAX_TEXT_ID 4                                       // used for SCRIPT_COMMAND_TALK, SCRIPT_COMMAND_EMOTE, SCRIPT_COMMAND_CAST_SPELL, SCRIPT_COMMAND_TERMINATE_SCRIPT

    enum ScriptInfoDataFlags
    {
        // default: s/b -> t
        SCRIPT_FLAG_BUDDY_AS_TARGET = 0x01,         // s -> b
        SCRIPT_FLAG_REVERSE_DIRECTION = 0x02,         // t* -> s* (* result after previous flag is evaluated)
        SCRIPT_FLAG_SOURCE_TARGETS_SELF = 0x04,         // s* -> s* (* result after previous flag is evaluated)
        SCRIPT_FLAG_COMMAND_ADDITIONAL = 0x08,         // command dependend
        SCRIPT_FLAG_BUDDY_BY_GUID = 0x10,         // take the buddy by guid
        SCRIPT_FLAG_BUDDY_IS_PET = 0x20,         // buddy is a pet
        SCRIPT_FLAG_BUDDY_IS_DESPAWNED = 0x40,         // buddy is dead or despawned
    };
#define MAX_SCRIPT_FLAG_VALID               (2 * SCRIPT_FLAG_BUDDY_IS_DESPAWNED - 1)

    struct ScriptInfo
    {
        unsigned int id;
        unsigned int delay;
        unsigned int command;

        union
        {
            // datalong unused                                  // SCRIPT_COMMAND_TALK (0)

            struct                                              // SCRIPT_COMMAND_EMOTE (1)
            {
                unsigned int emoteId;                                 // datalong
                unsigned int unused1;                                 // datalong2
            } emote;

            struct                                              // SCRIPT_COMMAND_FIELD_SET (2)
            {
                unsigned int fieldId;                                 // datalong
                unsigned int fieldValue;                              // datalong2
            } setField;

            struct                                              // SCRIPT_COMMAND_MOVE_TO (3)
            {
                unsigned int unused1;                                 // datalong
                unsigned int travelSpeed;                             // datalong2
            } moveTo;

            struct                                              // SCRIPT_COMMAND_FLAG_SET (4)
            {
                unsigned int fieldId;                                 // datalong
                unsigned int fieldValue;                              // datalong2
            } setFlag;

            struct                                              // SCRIPT_COMMAND_FLAG_REMOVE (5)
            {
                unsigned int fieldId;                                 // datalong
                unsigned int fieldValue;                              // datalong2
            } removeFlag;

            struct                                              // SCRIPT_COMMAND_TELEPORT_TO (6)
            {
                unsigned int mapId;                                   // datalong
                unsigned int empty;                                   // datalong2
            } teleportTo;

            struct                                              // SCRIPT_COMMAND_QUEST_EXPLORED (7)
            {
                unsigned int questId;                                 // datalong
                unsigned int distance;                                // datalong2
            } questExplored;

            struct                                              // SCRIPT_COMMAND_KILL_CREDIT (8)
            {
                unsigned int creatureEntry;                           // datalong
                unsigned int isGroupCredit;                           // datalong2
            } killCredit;

            struct                                              // SCRIPT_COMMAND_RESPAWN_GAMEOBJECT (9)
            {
                unsigned int goGuid;                                  // datalong
                unsigned int despawnDelay;                            // datalong2
            } respawnGo;

            struct                                              // SCRIPT_COMMAND_TEMP_SUMMON_CREATURE (10)
            {
                unsigned int creatureEntry;                           // datalong
                unsigned int despawnDelay;                            // datalong2
            } summonCreature;

            // datalong unused                                  // SCRIPT_COMMAND_OPEN_DOOR (11)

            struct                                              // SCRIPT_COMMAND_CLOSE_DOOR (12)
            {
                unsigned int goGuid;                                  // datalong
                unsigned int resetDelay;                              // datalong2
            } changeDoor;

            struct                                              // SCRIPT_COMMAND_ACTIVATE_OBJECT (13)
            {
                unsigned int empty1;                                  // datalong
                unsigned int empty2;                                  // datalong;
            } activateObject;

            struct                                              // SCRIPT_COMMAND_REMOVE_AURA (14)
            {
                unsigned int spellId;                                 // datalong
                unsigned int empty;                                   // datalong2
            } removeAura;

            struct                                              // SCRIPT_COMMAND_CAST_SPELL (15)
            {
                unsigned int spellId;                                 // datalong
                unsigned int empty;                                   // datalong2
            } castSpell;

            struct                                              // SCRIPT_COMMAND_PLAY_SOUND (16)
            {
                unsigned int soundId;                                 // datalong
                unsigned int flags;                                   // datalong2
            } playSound;

            struct                                              // SCRIPT_COMMAND_CREATE_ITEM (17)
            {
                unsigned int itemEntry;                               // datalong
                unsigned int amount;                                  // datalong2
            } createItem;

            struct                                              // SCRIPT_COMMAND_DESPAWN_SELF (18)
            {
                unsigned int despawnDelay;                            // datalong
                unsigned int empty;                                   // datalong2
            } despawn;

            struct                                              // SCRIPT_COMMAND_PLAY_MOVIE (19)
            {
                unsigned int movieId;                                 // datalong
                unsigned int empty;                                   // datalong2
            } playMovie;

            struct                                              // SCRIPT_COMMAND_MOVEMENT (20)
            {
                unsigned int movementType;                            // datalong
                unsigned int wanderDistance;                          // datalong2
            } movement;

            struct                                              // SCRIPT_COMMAND_SET_ACTIVEOBJECT (21)
            {
                unsigned int activate;                                // datalong
                unsigned int empty;                                   // datalong2
            } activeObject;

            struct                                              // SCRIPT_COMMAND_SET_FACTION (22)
            {
                unsigned int factionId;                               // datalong
                unsigned int flags;                                   // datalong2
            } faction;

            struct                                              // SCRIPT_COMMAND_MORPH_TO_ENTRY_OR_MODEL (23)
            {
                unsigned int creatureOrModelEntry;                    // datalong
                unsigned int empty1;                                  // datalong2
            } morph;

            struct                                              // SCRIPT_COMMAND_MOUNT_TO_ENTRY_OR_MODEL (24)
            {
                unsigned int creatureOrModelEntry;                    // datalong
                unsigned int empty1;                                  // datalong2
            } mount;

            struct                                              // SCRIPT_COMMAND_SET_RUN (25)
            {
                unsigned int run;                                     // datalong
                unsigned int empty;                                   // datalong2
            } run;

            // datalong unused                                  // SCRIPT_COMMAND_ATTACK_START (26)

            struct                                              // SCRIPT_COMMAND_GO_LOCK_STATE (27)
            {
                unsigned int lockState;                               // datalong
                unsigned int empty;                                   // datalong
            } goLockState;

            struct                                              // SCRIPT_COMMAND_STAND_STATE (28)
            {
                unsigned int stand_state;                             // datalong
                unsigned int unused1;                                 // datalong2
            } standState;

            struct                                              // SCRIPT_COMMAND_MODIFY_NPC_FLAGS (29)
            {
                unsigned int flag;                                    // datalong
                unsigned int change_flag;                             // datalong2
            } npcFlag;

            struct                                              // SCRIPT_COMMAND_SEND_TAXI_PATH (30)
            {
                unsigned int taxiPathId;                              // datalong
                unsigned int empty;
            } sendTaxiPath;

            struct                                              // SCRIPT_COMMAND_TERMINATE_SCRIPT (31)
            {
                unsigned int npcEntry;                                // datalong
                unsigned int searchDist;                              // datalong2
                // changeWaypointWaitTime                       // dataint
            } terminateScript;

            struct                                              // SCRIPT_COMMAND_PAUSE_WAYPOINTS (32)
            {
                unsigned int doPause;                                 // datalong
                unsigned int empty;
            } pauseWaypoint;

            struct                                              // SCRIPT_COMMAND_JOIN_LFG (33)
            {
                unsigned int areaId;                                  // datalong
            } joinLfg;

            struct                                              // SCRIPT_COMMAND_TERMINATE_COND (34)
            {
                unsigned int conditionId;                             // datalong
                unsigned int failQuest;                               // datalong2
            } terminateCond;

            struct                                              // SCRIPT_COMMAND_SEND_AI_EVENT_AROUND (35)
            {
                unsigned int eventType;                               // datalong
                unsigned int radius;                                  // datalong2
            } sendAIEvent;

            struct                                              // SCRIPT_COMMAND_TURN_TO (36)
            {
                unsigned int targetId;                                // datalong
                unsigned int empty1;                                  // datalong2
            } turnTo;

            struct                                              // SCRIPT_COMMAND_MOVE_DYNAMIC (37)
            {
                unsigned int maxDist;                                 // datalong
                unsigned int minDist;                                 // datalong2
            } moveDynamic;

            struct                                              // SCRIPT_COMMAND_SEND_MAIL (38)
            {
                unsigned int mailTemplateId;                          // datalong
                unsigned int altSender;                               // datalong2;
            } sendMail;

            struct                                              // SCRIPT_COMMAND_MORPH_TO_ENTRY_OR_MODEL (39)
            {
                unsigned int creatureEntry;                           // datalong
                unsigned int empty1;                                  // datalong2
            } changeEntry;

            struct                                              // SCRIPT_COMMAND_DESPAWN_GO (40)
            {
                unsigned int goGuid;                                  //datalong
                unsigned int respawnTime;                             //datalong2
            } despawnGo;
            // datalong unused                                  // SCRIPT_COMMAND_RESPAWN (41)

            struct                                              // SCRIPT_COMMAND_SET_EQUIPMENT_SLOTS (42)
            {
                unsigned int resetDefault;                            // datalong
                unsigned int empty;                                   // datalong2
            } setEquipment;

            // datalong unused                                  // SCRIPT_COMMAND_RESET_GO (43)

            struct                                              // SCRIPT_COMMAND_UPDATE_TEMPLATE (44)
            {
                unsigned int entry;                                   // datalong
                unsigned int faction;                                 // datalong2
            } updateTemplate;

            struct                                              // SCRIPT_COMMAND_XP_USER (53)
            {
                unsigned int flags;                                   // datalong
                unsigned int empty;                                   // datalong2
            } xpDisabled;

            struct                                              // SCRIPT_COMMAND_SET_FLY (59)
            {
                unsigned int enable;                                  // datalong
                unsigned int empty;                                   // datalong2
            } fly;

            struct
            {
                unsigned int data[2];
            } raw;
        };

        // Buddy system (entry can be npc or go entry, depending on command)
        unsigned int buddyEntry;                                      // buddy_entry
        unsigned int searchRadiusOrGuid;                              // search_radius (can also be guid in case of SCRIPT_FLAG_BUDDY_BY_GUID)
        unsigned char data_flags;                                       // data_flags

        int textId[MAX_TEXT_ID];                              // dataint to dataint4

        float x;
        float y;
        float z;
        float o;

        // helpers
        unsigned int GetGOGuid() const
        {
            switch (command)
            {
            case SCRIPT_COMMAND_RESPAWN_GO:
                return respawnGo.goGuid;
            case SCRIPT_COMMAND_DESPAWN_GO:
                return despawnGo.goGuid;
            case SCRIPT_COMMAND_OPEN_DOOR:
            case SCRIPT_COMMAND_CLOSE_DOOR:
                return changeDoor.goGuid;
            default:
                return 0;
            }
        }

        bool IsCreatureBuddy() const
        {
            switch (command)
            {
            case SCRIPT_COMMAND_RESPAWN_GO:
            case SCRIPT_COMMAND_OPEN_DOOR:
            case SCRIPT_COMMAND_CLOSE_DOOR:
            case SCRIPT_COMMAND_ACTIVATE_OBJECT:
            case SCRIPT_COMMAND_GO_LOCK_STATE:
            case SCRIPT_COMMAND_DESPAWN_GO:
            case SCRIPT_COMMAND_RESET_GO:
                return false;
            default:
                return true;
            }
        }

        bool HasAdditionalScriptFlag() const
        {
            switch (command)
            {
            case SCRIPT_COMMAND_MOVE_TO:
            case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE:
            case SCRIPT_COMMAND_CAST_SPELL:
            case SCRIPT_COMMAND_PLAY_SOUND:
            case SCRIPT_COMMAND_MOVEMENT:
            case SCRIPT_COMMAND_MORPH_TO_ENTRY_OR_MODEL:
            case SCRIPT_COMMAND_MOUNT_TO_ENTRY_OR_MODEL:
            case SCRIPT_COMMAND_TERMINATE_SCRIPT:
            case SCRIPT_COMMAND_TERMINATE_COND:
            case SCRIPT_COMMAND_TURN_TO:
            case SCRIPT_COMMAND_MOVE_DYNAMIC:
            case SCRIPT_COMMAND_SET_FLY:
                return true;
            default:
                return false;
            }
        }
    };

    typedef std::vector < ScriptInfo > ScriptChain;
    typedef std::map < unsigned int /*id*/, ScriptChain > ScriptChainMap;
    typedef std::vector < ScriptChainMap > DBScripts;

    class ScriptAction
    {
    public:
        ScriptAction(DBScriptType _type, Map* _map, ObjectGuid _sourceGuid, ObjectGuid _targetGuid, ObjectGuid _ownerGuid, ScriptInfo const* _script) :
            m_type(_type), m_map(_map), m_sourceGuid(_sourceGuid), m_targetGuid(_targetGuid), m_ownerGuid(_ownerGuid), m_script(_script)
        {
        }

        bool HandleScriptStep();                            // return true IF AND ONLY IF the script should be terminated

        DBScriptType GetType() const
        {
            return m_type;
        }
        unsigned int GetId() const
        {
            return m_script->id;
        }
        ObjectGuid GetSourceGuid() const
        {
            return m_sourceGuid;
        }
        ObjectGuid GetTargetGuid() const
        {
            return m_targetGuid;
        }
        ObjectGuid GetOwnerGuid() const
        {
            return m_ownerGuid;
        }

        bool IsSameScript(DBScriptType type, unsigned int id, ObjectGuid sourceGuid, ObjectGuid targetGuid, ObjectGuid ownerGuid) const
        {
            return type == m_type && id == GetId() &&
                (sourceGuid == m_sourceGuid || !sourceGuid) &&
                (targetGuid == m_targetGuid || !targetGuid) &&
                (ownerGuid == m_ownerGuid || !ownerGuid);
        }

    private:
        DBScriptType m_type;                                // which type has the script was started
        Map* m_map;                                         // Map on which the action will be executed
        ObjectGuid m_sourceGuid;
        ObjectGuid m_targetGuid;
        ObjectGuid m_ownerGuid;                             // owner of source if source is item
        ScriptInfo const* m_script;                         // pointer to static script data

        // Helper functions
        bool GetScriptCommandObject(const ObjectGuid guid, bool includeItem, battle::object::Object*& resultObject);
        bool GetScriptProcessTargets(battle::object::WorldObject* pOrigSource, battle::object::WorldObject* pOrigTarget, battle::object::WorldObject*& pFinalSource, battle::object::WorldObject*& pFinalTarget);
        bool LogIfNotCreature(battle::object::WorldObject* pWorldObject);
        bool LogIfNotUnit(battle::object::WorldObject* pWorldObject);
        bool LogIfNotGameObject(battle::object::WorldObject* pWorldObject);
        bool LogIfNotPlayer(battle::object::WorldObject* pWorldObject);
        battle::object::Player* GetPlayerTargetOrSourceAndLog(battle::object::WorldObject* pSource, battle::object::WorldObject* pTarget);
    };


    enum ScriptLoadResult
    {
        SCRIPT_LOAD_OK,
        SCRIPT_LOAD_ERR_NOT_FOUND,
        SCRIPT_LOAD_ERR_WRONG_API,
        SCRIPT_LOAD_ERR_OUTDATED,
    };

    class ScriptMgr : public cftf::util::Singleton<ScriptMgr>
    {
        friend class cftf::util::Singleton<ScriptMgr>;
        ScriptMgr();
    public:
        ~ScriptMgr();

        std::string GenerateNameToId(ScriptedObjectType sot, unsigned int id);

        void LoadDbScripts(DBScriptType type);
        void LoadDbScriptStrings();

        void LoadScriptNames();
        void LoadScriptBinding();
        void LoadAreaTriggerScripts();
        void LoadEventIdScripts();
        void LoadSpellIdScripts();

        unsigned int GetAreaTriggerScriptId(unsigned int triggerId) const;
        unsigned int GetEventIdScriptId(unsigned int eventId) const;

        bool ReloadScriptBinding();

        ScriptChainMap const* GetScriptChainMap(DBScriptType type);

        const char* GetScriptName(unsigned int id) const
        {
            return id < m_scriptNames.size() ? m_scriptNames[id].c_str() : "";
        }

        unsigned int GetScriptId(const char* name) const;

        unsigned int GetScriptIdsCount() const
        {
            return (unsigned int)m_scriptNames.size();
        }

        unsigned int GetBoundScriptId(ScriptedObjectType entity, int entry);

        ScriptLoadResult LoadScriptLibrary(const char* libName);
        void UnloadScriptLibrary();
        bool IsScriptLibraryLoaded() const
        {
#ifdef ENABLE_SD3
            return true;
#else
            return false;
#endif
        }

        unsigned int IncreaseScheduledScriptsCount()
        {
            return (unsigned int)++m_scheduledScripts;
        }
        unsigned int DecreaseScheduledScriptCount()
        {
            return (unsigned int)--m_scheduledScripts;
        }
        unsigned int DecreaseScheduledScriptCount(size_t count)
        {
            return (unsigned int)(m_scheduledScripts -= (decltype(m_scheduledScripts))count);
        }
        bool IsScriptScheduled() const
        {
            return m_scheduledScripts > 0;
        }
        static bool CanSpellEffectStartDBScript(SpellEntry const* spellinfo, SpellEffectIndex effIdx);

        CreatureAI* GetCreatureAI(battle::object::Creature* pCreature);

        GameObjectAI* GetGameObjectAI(battle::object::GameObject* pGo);

        InstanceData* CreateInstanceData(Map* pMap);

        char const* GetScriptLibraryVersion() const;
        bool OnGossipHello(battle::object::Player* pPlayer, battle::object::Creature* pCreature);
        bool OnGossipHello(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject);
        bool OnGossipHello(battle::object::Player* pPlayer, Item* pItem);
        bool OnGossipSelect(battle::object::Player* pPlayer, battle::object::Creature* pCreature, unsigned int sender, unsigned int action, const char* code);
        bool OnGossipSelect(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject, unsigned int sender, unsigned int action, const char* code);
        bool OnGossipSelect(battle::object::Player* pPlayer, Item* pItem, unsigned int sender, unsigned int action, const char* code);
        bool OnQuestAccept(battle::object::Player* pPlayer, battle::object::Creature* pCreature, Quest const* pQuest);
        bool OnQuestAccept(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject, Quest const* pQuest);
        bool OnQuestAccept(battle::object::Player* pPlayer, Item* pItem, Quest const* pQuest);
        bool OnQuestRewarded(battle::object::Player* pPlayer, battle::object::Creature* pCreature, Quest const* pQuest, unsigned int reward);
        bool OnQuestRewarded(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject, Quest const* pQuest, unsigned int reward);
        unsigned int GetDialogStatus(battle::object::Player* pPlayer, battle::object::Creature* pCreature);
        unsigned int GetDialogStatus(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject);
        bool OnGameObjectUse(battle::object::Player* pPlayer, battle::object::GameObject* pGameObject);
        bool OnGameObjectUse(battle::object::Unit* pUnit, battle::object::GameObject* pGameObject);
        bool OnItemUse(battle::object::Player* pPlayer, Item* pItem, SpellCastTargets const& targets);
        bool OnAreaTrigger(battle::object::Player* pPlayer, AreaTriggerEntry const* atEntry);
        bool OnNpcSpellClick(battle::object::Player* pPlayer, battle::object::Creature* pClickedCreature, unsigned int spellId);
        bool OnProcessEvent(unsigned int eventId, battle::object::Object* pSource, battle::object::Object* pTarget, bool isStart);
        bool OnEffectDummy(battle::object::Unit* pCaster, unsigned int spellId, SpellEffectIndex effIndex, battle::object::Unit* pTarget, ObjectGuid originalCasterGuid);
        bool OnEffectDummy(battle::object::Unit* pCaster, unsigned int spellId, SpellEffectIndex effIndex, battle::object::GameObject* pTarget, ObjectGuid originalCasterGuid);
        bool OnEffectDummy(battle::object::Unit* pCaster, unsigned int spellId, SpellEffectIndex effIndex, Item* pTarget, ObjectGuid originalCasterGuid);
        bool OnEffectScriptEffect(battle::object::Unit* pCaster, unsigned int spellId, SpellEffectIndex effIndex, battle::object::Unit* pTarget, ObjectGuid originalCasterGuid);
        bool OnAuraDummy(Aura const* pAura, bool apply);

    private:
        void CollectPossibleEventIds(std::set<unsigned int>& eventIds);
        void LoadScripts(DBScriptType type);
        void CheckScriptTexts(std::set<int>& ids);

        typedef std::vector<std::string> ScriptNameMap;
        typedef std::unordered_map<int, unsigned int> EntryToScriptIdMap;

        EntryToScriptIdMap m_scriptBind[SCRIPTED_MAX_TYPE];

        ScriptNameMap      m_scriptNames;
        DBScripts          m_dbScripts;
#if 1
        long m_scheduledScripts;
#else
#ifdef _DEBUG
        // mutex allowing to reload the script binding table; TODO just do it AWAY from any map update, e.g. right after sessions update
        ACE_RW_Thread_Mutex m_bindMutex;
#endif /* _DEBUG */
        // atomic op counter for active scripts amount
        ACE_Atomic_Op<ACE_Thread_Mutex, long> m_scheduledScripts;
        char __cache_guard[1024];
        ACE_Thread_Mutex m_lock;
#endif
    };

    // Starters for events
    bool StartEvents_Event(Map* map, unsigned int id, battle::object::Object* source, battle::object::Object* target, bool isStart = true, battle::object::Unit* forwardToPvp = NULL);

    unsigned int GetScriptId(const char* name);
    char const* GetScriptName(unsigned int id);
    unsigned int GetScriptIdsCount();
    unsigned int GetAreaTriggerScriptId(unsigned int triggerId);
    unsigned int GetEventIdScriptId(unsigned int eventId);
    void SetExternalWaypointTable(char const* tableName);
    bool AddWaypointFromExternal(unsigned int entry, int pathId, unsigned int pointId, float x, float y, float z, float o, unsigned int waittime);

}